import {
  ClaudeRequest,
  ClaudeResponse,
  ClaudeMessage,
  ClaudeContent,
  ClaudeTool,
  OpenAIRequest,
  OpenAIResponse,
  OpenAIMessage,
  OpenAITool,
  OpenAIToolCall,
  Config,
  APIError,
} from './types.js';

export class ApiConverter {
  // eslint-disable-next-line no-unused-vars
  constructor(private config: Config) {}

  private log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: unknown): void {
    const levels = { debug: 0, info: 1, warn: 2, error: 3 };
    const configLevel = levels[this.config.logLevel];
    const messageLevel = levels[level];

    if (messageLevel >= configLevel) {
      const timestamp = new Date().toISOString();
      const logData = data ? ` ${JSON.stringify(data)}` : '';
      console[level](`[${timestamp}] ${message}${logData}`);
    }
  }

  claudeToOpenAI(claudeRequest: ClaudeRequest): OpenAIRequest {
    this.log('debug', '🔄 Converting Claude request to OpenAI format', {
      model: claudeRequest.model,
      messageCount: claudeRequest.messages.length,
      hasTools: !!claudeRequest.tools?.length,
      streaming: claudeRequest.stream,
    });

    // Convert messages
    const messages: OpenAIMessage[] = [];

    // Add system message if present
    if (claudeRequest.system) {
      const systemText = Array.isArray(claudeRequest.system)
        ? claudeRequest.system
            .filter((c) => c.type === 'text')
            .map((c) => c.text)
            .join('\n\n')
        : claudeRequest.system;

      if (systemText) {
        messages.push({
          role: 'system',
          content: systemText,
        });
      }
    }

    // Convert Claude messages to OpenAI format
    for (const message of claudeRequest.messages) {
      const convertedMessage = this.convertClaudeMessage(message);
      if (Array.isArray(convertedMessage)) {
        messages.push(...convertedMessage);
      } else {
        messages.push(convertedMessage);
      }
    }

    // Handle token limits
    const requestedTokens = claudeRequest.max_tokens || 1000;
    const safeTokens = Math.min(requestedTokens, this.config.maxTokens);

    if (requestedTokens > this.config.maxTokens) {
      this.log('warn', `Token limit exceeded: requested ${requestedTokens}, capped at ${safeTokens}`);
    }

    const openaiRequest: OpenAIRequest = {
      model: this.config.model,
      messages,
      max_tokens: safeTokens,
    };

    // Add optional properties only if they exist
    if (claudeRequest.temperature !== undefined) {
      openaiRequest.temperature = claudeRequest.temperature;
    }
    if (claudeRequest.top_p !== undefined) {
      openaiRequest.top_p = claudeRequest.top_p;
    }
    
    // Force disable streaming for simplicity
    openaiRequest.stream = false;

    // Convert tools if present
    if (claudeRequest.tools && claudeRequest.tools.length > 0) {
      openaiRequest.tools = claudeRequest.tools.map(this.convertClaudeTool);
    }

    this.log('debug', '✅ Claude to OpenAI conversion complete', {
      model: openaiRequest.model,
      messageCount: openaiRequest.messages.length,
      maxTokens: openaiRequest.max_tokens,
    });

    return openaiRequest;
  }

  openAIToClaude(openaiResponse: OpenAIResponse, originalModel: string): ClaudeResponse {
    // Add robust validation for the OpenAI response
    if (!openaiResponse || !openaiResponse.choices || openaiResponse.choices.length === 0) {
      this.log('error', '❌ Invalid or empty response from OpenAI API', openaiResponse);
      if (openaiResponse && (openaiResponse as any).error) {
        const error = (openaiResponse as any).error;
        const errorMessage = typeof error === 'string' ? error : error.message || JSON.stringify(error);
        throw new APIError(`API returned an error: ${errorMessage}`);
      }
      throw new APIError('Invalid or empty response from OpenAI API: No choices found');
    }

    this.log('debug', '🔄 Converting OpenAI response to Claude format', {
      id: openaiResponse.id,
      model: openaiResponse.model,
      choices: openaiResponse.choices.length,
    });

    const choice = openaiResponse.choices[0];
    if (!choice) {
      throw new APIError('No choices in OpenAI response');
    }

    const message = choice.message;
    const content: ClaudeContent[] = [];

    // Add text content
    if (message.content) {
      content.push({
        type: 'text',
        text: message.content,
      });
    }

    // Add tool calls if present
    if (message.tool_calls && message.tool_calls.length > 0) {
      for (const toolCall of message.tool_calls) {
        content.push({
          type: 'tool_use',
          id: toolCall.id,
          name: toolCall.function.name,
          input: JSON.parse(toolCall.function.arguments),
        });
      }
    }

    // Map finish reason
    const stopReasonMap: Record<string, ClaudeResponse['stop_reason']> = {
      stop: 'end_turn',
      length: 'max_tokens',
      tool_calls: 'tool_use',
      content_filter: 'end_turn',
    };

    const claudeResponse: ClaudeResponse = {
      id: openaiResponse.id,
      type: 'message',
      role: 'assistant',
      model: originalModel, // Use original Claude model name
      content,
      stop_reason: stopReasonMap[choice.finish_reason] || 'end_turn',
      stop_sequence: null,
      usage: {
        input_tokens: openaiResponse.usage.prompt_tokens,
        output_tokens: openaiResponse.usage.completion_tokens,
      },
    };

    this.log('debug', '✅ OpenAI to Claude conversion complete', {
      id: claudeResponse.id,
      contentItems: claudeResponse.content.length,
      stopReason: claudeResponse.stop_reason,
    });

    return claudeResponse;
  }

  private convertClaudeMessage(message: ClaudeMessage): OpenAIMessage | OpenAIMessage[] {
    if (typeof message.content === 'string') {
      return {
        role: message.role === 'user' ? 'user' : message.role === 'assistant' ? 'assistant' : 'system',
        content: message.content,
      };
    }

    // Handle complex content
    const messages: OpenAIMessage[] = [];
    const textParts: string[] = [];
    const toolCalls: OpenAIToolCall[] = [];

    for (const contentItem of message.content) {
      switch (contentItem.type) {
        case 'text':
          if (contentItem.text) {
            textParts.push(contentItem.text);
          }
          break;

        case 'cache_control':
          // ignore cache_control
          break;

        case 'image':
          // For now, convert image to text description
          textParts.push('[Image content not supported in OpenAI conversion]');
          this.log('warn', 'Image content detected but not fully supported in conversion');
          break;

        case 'tool_use':
          if (contentItem.id && contentItem.name && contentItem.input) {
            toolCalls.push({
              id: contentItem.id,
              type: 'function',
              function: {
                name: contentItem.name,
                arguments: JSON.stringify(contentItem.input),
              },
            });
          }
          break;

        case 'tool_result':
          if (contentItem.tool_use_id && contentItem.content) {
            messages.push({
              role: 'tool',
              content: typeof contentItem.content === 'string' ? contentItem.content : JSON.stringify(contentItem.content),
              tool_call_id: contentItem.tool_use_id,
            });
          }
          break;
      }
    }

    // Create main message
    const mainMessage: OpenAIMessage = {
      role: message.role === 'user' ? 'user' : message.role === 'assistant' ? 'assistant' : 'system',
      content: textParts.length > 0 ? textParts.join('\n\n') : null,
    };

    if (toolCalls.length > 0) {
      mainMessage.tool_calls = toolCalls;
    }

    return [mainMessage, ...messages];
  }

  private convertClaudeTool(tool: ClaudeTool): OpenAITool {
    return {
      type: 'function',
      function: {
        name: tool.name,
        description: tool.description,
        parameters: tool.input_schema,
      },
    };
  }

  async makeRequest(openaiRequest: OpenAIRequest): Promise<OpenAIResponse> {
    const url = `${this.config.url.replace(/\/$/, '')}/chat/completions`;
    
    this.log('debug', `🔗 Making request to: ${url}`);
    this.log('debug', '📤 Request payload:', openaiRequest);
    this.log('debug', '🔑 Using API key:', this.config.apiKey.substring(0, 12) + '...');

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.config.timeout * 1000);

    try {
      const requestBody = JSON.stringify(openaiRequest);
      this.log('debug', '📝 Request body size:', requestBody.length);
      
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.config.apiKey}`,
        },
        body: requestBody,
        signal: controller.signal,
      });
      
      this.log('debug', '📨 Response status:', response.status);
      this.log('debug', '📨 Response headers:', Object.fromEntries(response.headers.entries()));

      if (!response.ok) {
        const errorText = await response.text();
        this.log('error', `❌ API Error: ${response.status}`, {
          status: response.status,
          statusText: response.statusText,
          errorText,
          url,
        });
        throw new APIError(`API Error: ${response.status} ${errorText}`, response.status);
      }

      const data = await response.json() as OpenAIResponse;
      
      this.log('debug', '✅ API Response received', {
        id: data.id,
        model: data.model,
        usage: data.usage,
      });

      return data;
    } catch (error) {
      if (error instanceof APIError) {
        throw error;
      }
      
      if (error instanceof Error && error.name === 'AbortError') {
        this.log('error', '❌ Request timeout', { timeout: this.config.timeout });
        throw new APIError(`Request timeout after ${this.config.timeout} seconds`, 408);
      }

      this.log('error', '❌ Request failed', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        url,
        config: {
          timeout: this.config.timeout,
          url: this.config.url,
        },
      });
      throw new APIError(`Request failed: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      clearTimeout(timeoutId);
    }
  }
}